/**
 * 虚拟文件系统哈希工具
 * 提供高性能的文件内容哈希计算功能
 */

import { ITypeEntity } from '@/types/modules/superType/types';
import { LcDocument, LcProject } from '@/types/modules/editor/types/editor.type';

export interface HashOptions {
  /** 哈希算法，默认 SHA-256 */
  algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';
  /** 分块大小，默认 64KB */
  chunkSize?: number;
  /** 是否启用缓存，默认 true */
  enableCache?: boolean;
}

export interface HashResult {
  /** 哈希值 */
  hash: string;
  /** 内容大小（字节） */
  size: number;
  /** 计算耗时（毫秒） */
  duration: number;
  /** 是否来自缓存 */
  fromCache: boolean;
}

/**
 * 哈希缓存管理
 */
class HashCache {
  private cache = new Map<string, { hash: string; size: number; timestamp: number }>();
  private maxSize = 1000; // 最大缓存条目数
  private maxAge = 5 * 60 * 1000; // 5分钟过期

  set(content: string, hash: string, size: number) {
    // 清理过期缓存
    this.cleanup();

    // 如果缓存已满，清理最老的条目
    if (this.cache.size >= this.maxSize) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }

    this.cache.set(content, {
      hash,
      size,
      timestamp: Date.now(),
    });
  }

  get(content: string): { hash: string; size: number } | null {
    const cached = this.cache.get(content);
    if (!cached) return null;

    // 检查是否过期
    if (Date.now() - cached.timestamp > this.maxAge) {
      this.cache.delete(content);
      return null;
    }

    return { hash: cached.hash, size: cached.size };
  }

  private cleanup() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > this.maxAge) {
        this.cache.delete(key);
      }
    }
  }

  clear() {
    this.cache.clear();
  }

  getStats() {
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      maxAge: this.maxAge,
    };
  }
}

// 全局哈希缓存实例
const hashCache = new HashCache();

export async function calculateProjectHash(project: LcProject) {
  const {
    update_time,
    create_time,
    hash,
    version,
    documents,
    lastUpdateBy,
    teamId,
    userId,
    schemaPartial,
    allowCopy,
    allowCopyRole,
    canEdit,
    member,
    followUserIds,
    isTemplate,
    templateTags,
    links,
    _id,
    ...contentData
  } = project;
  const content = JSON.stringify(contentData, null, 2);
  return calculateFileHash(content, {
    algorithm: 'SHA-256',
    enableCache: true,
  });
}

export async function calculateDocumentHash(doc: LcDocument) {
  const content = JSON.stringify(
    {
      schema: doc.schema,
      title: doc.title,
      description: doc.description,
      width: doc.width,
      height: doc.height,
      route: doc.route,
      frames: doc.frames,
      clients: doc.clients,
      portal: doc.portal,
      backgroundColor: doc.backgroundColor,
      types: doc.types,
    },
    null,
    2,
  );
  return calculateFileHash(content, {
    algorithm: 'SHA-256',
    enableCache: true,
  });
}

export async function calculateTypeHash(typeEntity: ITypeEntity) {
  const {
    update_time,
    create_time,
    hash,
    version,
    _id,
    fkId,
    unikey,
    teamId,
    projectId,
    folder,
    folderPath,
    ...contentData
  } = typeEntity;
  const content = JSON.stringify(contentData, null, 2);
  return calculateFileHash(content, {
    algorithm: 'SHA-256',
    enableCache: true,
  });
}

/**
 * 计算字符串内容的哈希值
 * 支持大文件分块处理和缓存
 */
export async function calculateFileHash(
  content: string,
  options: HashOptions = {},
): Promise<HashResult> {
  const startTime = performance.now();
  const {
    algorithm = 'SHA-256',
    chunkSize = 64 * 1024, // 64KB
    enableCache = true,
  } = options;

  const size = new TextEncoder().encode(content).length;

  // 检查缓存
  if (enableCache) {
    const cached = hashCache.get(content);
    if (cached) {
      return {
        hash: cached.hash,
        size: cached.size,
        duration: performance.now() - startTime,
        fromCache: true,
      };
    }
  }

  let hash: string;

  try {
    // 检查是否支持 Web Crypto API
    if (!crypto?.subtle) {
      throw new Error('Web Crypto API not supported');
    }

    // 小文件直接计算
    if (size <= chunkSize) {
      hash = await calculateHashDirect(content, algorithm);
    } else {
      // 大文件分块计算
      hash = await calculateHashChunked(content, algorithm, chunkSize);
    }

    // 缓存结果
    if (enableCache) {
      hashCache.set(content, hash, size);
    }

    const duration = performance.now() - startTime;

    console.log(
      `[HashUtils] 计算哈希完成: ${size} 字节, ${duration.toFixed(2)}ms, 算法: ${algorithm}`,
    );

    return {
      hash,
      size,
      duration,
      fromCache: false,
    };
  } catch (error) {
    console.warn('[HashUtils] Web Crypto API 失败，使用简单哈希:', error);

    // 降级到简单哈希
    hash = calculateSimpleHash(content);

    if (enableCache) {
      hashCache.set(content, hash, size);
    }

    return {
      hash,
      size,
      duration: performance.now() - startTime,
      fromCache: false,
    };
  }
}

/**
 * 直接计算哈希（小文件）
 */
async function calculateHashDirect(content: string, algorithm: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(content);
  const hashBuffer = await crypto.subtle.digest(algorithm, data);
  return bufferToHex(hashBuffer);
}

/**
 * 分块计算哈希（大文件）
 */
async function calculateHashChunked(
  content: string,
  algorithm: string,
  chunkSize: number,
): Promise<string> {
  const encoder = new TextEncoder();
  const totalBytes = encoder.encode(content).length;

  console.log(`[HashUtils] 开始分块计算哈希: ${totalBytes} 字节, 块大小: ${chunkSize}`);

  // 创建哈希上下文
  const chunks: Uint8Array[] = [];
  let offset = 0;

  while (offset < content.length) {
    const chunk = content.slice(offset, offset + chunkSize);
    const chunkData = encoder.encode(chunk);
    chunks.push(chunkData);
    offset += chunkSize;

    // 让出控制权，避免阻塞UI
    if (chunks.length % 10 === 0) {
      await new Promise((resolve) => setTimeout(resolve, 0));
    }
  }

  // 合并所有块并计算哈希
  const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
  const combined = new Uint8Array(totalLength);
  let position = 0;

  for (const chunk of chunks) {
    combined.set(chunk, position);
    position += chunk.length;
  }

  const hashBuffer = await crypto.subtle.digest(algorithm, combined);
  return bufferToHex(hashBuffer);
}

/**
 * 简单哈希算法（降级方案）
 */
function calculateSimpleHash(content: string): string {
  let hash = 0;
  for (let i = 0; i < content.length; i++) {
    const char = content.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash; // 转换为32位整数
  }
  return Math.abs(hash).toString(16).padStart(8, '0');
}

/**
 * 将 ArrayBuffer 转换为十六进制字符串
 */
function bufferToHex(buffer: ArrayBuffer): string {
  const hashArray = Array.from(new Uint8Array(buffer));
  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
}

/**
 * 批量计算多个文件的哈希
 */
export async function calculateMultipleFileHashes(
  files: Array<{ path: string; content: string }>,
  options: HashOptions = {},
): Promise<Array<{ path: string; result: HashResult }>> {
  const results: Array<{ path: string; result: HashResult }> = [];

  console.log(`[HashUtils] 开始批量计算哈希: ${files.length} 个文件`);

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const result = await calculateFileHash(file.content, options);
    results.push({ path: file.path, result });

    // 每处理10个文件让出一次控制权
    if (i % 10 === 9) {
      await new Promise((resolve) => setTimeout(resolve, 0));
    }
  }

  console.log(`[HashUtils] 批量哈希计算完成: ${files.length} 个文件`);

  return results;
}

/**
 * 获取哈希缓存统计信息
 */
export function getHashCacheStats() {
  return hashCache.getStats();
}

/**
 * 清理哈希缓存
 */
export function clearHashCache() {
  hashCache.clear();
}

/**
 * 比较两个文件内容是否相同（基于哈希）
 */
export async function compareFileContents(
  content1: string,
  content2: string,
  options: HashOptions = {},
): Promise<boolean> {
  // 快速检查：长度不同则内容必定不同
  if (content1.length !== content2.length) {
    return false;
  }

  // 如果内容完全相同，直接返回
  if (content1 === content2) {
    return true;
  }

  // 计算哈希进行比较
  const [hash1, hash2] = await Promise.all([
    calculateFileHash(content1, options),
    calculateFileHash(content2, options),
  ]);

  return hash1.hash === hash2.hash;
}
